<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>js音频可视化</title>
  <style>
    #file {
      position: fixed;
      top: 10px;
      left: 10px;
      z-index: 100;
      color: red;
    }

    #canvas {
      position: fixed;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
    }

    #audio {
      position: fixed;
      left: 10px;
      bottom: 10px;
      width: calc(100% - 20px);
    }

    body {
      margin: 1em;
      text-align: center;
      background-color: #1d1f20;
    }

    .desc {
      color: red;
    }

    .disc {
      display: inline-block;
      width: 60vmin;
      height: 60vmin;
      margin: 1em;
      border-radius: 50%;
      -webkit-animation: spin 6s linear infinite;
      animation: spin 6s linear infinite;
    }

    @-webkit-keyframes spin {
      to {
        -webkit-transform: rotate(1turn);
        transform: rotate(1turn);
      }
    }

    @keyframes spin {
      to {
        -webkit-transform: rotate(1turn);
        transform: rotate(1turn);
      }
    }

    .disc:hover {
      -webkit-animation-play-state: paused;
      animation-play-state: paused;
    }

    .disc:nth-child(1) {
      -webkit-animation-delay: -1.2s;
      animation-delay: -1.2s;
    }

    .disc:nth-child(2) {
      -webkit-animation-delay: -2.4s;
      animation-delay: -2.4s;
    }

    .disc:nth-child(3) {
      -webkit-animation-delay: -3.6s;
      animation-delay: -3.6s;
    }

    .disc:nth-child(4) {
      -webkit-animation-delay: -4.8s;
      animation-delay: -4.8s;
    }

    .disc:nth-child(5) {
      -webkit-animation-delay: -6s;
      animation-delay: -6s;
    }

    .record.black {
      background-color: #111;
      background-image: radial-gradient(circle, #eee 0, #eee 1.8%, rgba(17, 17, 17, 0.25) 0, rgba(17, 17, 17, 0.25) 24%, rgba(255, 255, 255, 0) 24%, rgba(255, 255, 255, 0) 100%), conic-gradient(rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 5%, rgba(255, 255, 255, 0.25) 8%, rgba(255, 255, 255, 0.25) 18%, rgba(255, 255, 255, 0.05) 24%, rgba(255, 255, 255, 0.05) 32%, rgba(255, 255, 255, 0.1) 34%, rgba(255, 255, 255, 0.1) 42%, rgba(255, 255, 255, 0.05) 46%, rgba(255, 255, 255, 0.05) 56%, rgba(255, 255, 255, 0.15) 64%, rgba(255, 255, 255, 0.15) 69%, rgba(255, 255, 255, 0.05) 72%, rgba(255, 255, 255, 0.05) 81%, rgba(255, 255, 255, 0.1) 84%, rgba(255, 255, 255, 0.1) 86%, rgba(255, 255, 255, 0.05) 90%, rgba(255, 255, 255, 0.05) 95%, rgba(255, 255, 255, 0.15) 98%, rgba(255, 255, 255, 0.15) 100%), radial-gradient(circle, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 29%, rgba(255, 255, 255, 0.05) 0, rgba(255, 255, 255, 0.05) 34.5%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0.05) 0, rgba(255, 255, 255, 0.05) 42%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 42.5%, rgba(255, 255, 255, 0.05) 0, rgba(255, 255, 255, 0.05) 48%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 48.25%, rgba(255, 255, 255, 0.05) 0, rgba(255, 255, 255, 0.05) 54.5%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 55%, rgba(255, 255, 255, 0.05) 0, rgba(255, 255, 255, 0.05) 59%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 59.5%, rgba(255, 255, 255, 0.05) 0, rgba(255, 255, 255, 0.05) 68%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 100%);
    }

    .record.bronze {
      background-color: #9e501e;
      background-image: radial-gradient(circle, #eee 0, #eee 1.8%, rgba(17, 17, 17, 0.75) 0, rgba(17, 17, 17, 0.75) 24%, rgba(255, 255, 255, 0) 24%, rgba(255, 255, 255, 0) 100%), conic-gradient(rgba(255, 255, 255, 0.45) 0, rgba(255, 255, 255, 0.45) 5%, rgba(255, 255, 255, 0.75) 8%, rgba(255, 255, 255, 0.75) 18%, rgba(255, 255, 255, 0.15) 24%, rgba(255, 255, 255, 0.15) 32%, rgba(255, 255, 255, 0.3) 34%, rgba(255, 255, 255, 0.3) 42%, rgba(255, 255, 255, 0.15) 46%, rgba(255, 255, 255, 0.15) 56%, rgba(255, 255, 255, 0.45) 64%, rgba(255, 255, 255, 0.45) 69%, rgba(255, 255, 255, 0.15) 72%, rgba(255, 255, 255, 0.15) 81%, rgba(255, 255, 255, 0.3) 84%, rgba(255, 255, 255, 0.3) 86%, rgba(255, 255, 255, 0.15) 90%, rgba(255, 255, 255, 0.15) 95%, rgba(255, 255, 255, 0.45) 98%, rgba(255, 255, 255, 0.45) 100%), radial-gradient(circle, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 29%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 34.5%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 42%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 42.5%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 48%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 48.25%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 54.5%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 55%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 59%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 59.5%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 68%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 100%);
      background-blend-mode: overlay;
    }

    .record.silver {
      background-color: #888;
      background-image: radial-gradient(circle, #eee 0, #eee 1.8%, rgba(17, 17, 17, 0.5) 0, rgba(17, 17, 17, 0.5) 24%, rgba(255, 255, 255, 0) 24%, rgba(255, 255, 255, 0) 100%), conic-gradient(rgba(255, 255, 255, 0.3) 0, rgba(255, 255, 255, 0.3) 5%, rgba(255, 255, 255, 0.5) 8%, rgba(255, 255, 255, 0.5) 18%, rgba(255, 255, 255, 0.1) 24%, rgba(255, 255, 255, 0.1) 32%, rgba(255, 255, 255, 0.2) 34%, rgba(255, 255, 255, 0.2) 42%, rgba(255, 255, 255, 0.1) 46%, rgba(255, 255, 255, 0.1) 56%, rgba(255, 255, 255, 0.3) 64%, rgba(255, 255, 255, 0.3) 69%, rgba(255, 255, 255, 0.1) 72%, rgba(255, 255, 255, 0.1) 81%, rgba(255, 255, 255, 0.2) 84%, rgba(255, 255, 255, 0.2) 86%, rgba(255, 255, 255, 0.1) 90%, rgba(255, 255, 255, 0.1) 95%, rgba(255, 255, 255, 0.3) 98%, rgba(255, 255, 255, 0.3) 100%), radial-gradient(circle, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 29%, rgba(255, 255, 255, 0.1) 0, rgba(255, 255, 255, 0.1) 34.5%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0.1) 0, rgba(255, 255, 255, 0.1) 42%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 42.5%, rgba(255, 255, 255, 0.1) 0, rgba(255, 255, 255, 0.1) 48%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 48.25%, rgba(255, 255, 255, 0.1) 0, rgba(255, 255, 255, 0.1) 54.5%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 55%, rgba(255, 255, 255, 0.1) 0, rgba(255, 255, 255, 0.1) 59%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 59.5%, rgba(255, 255, 255, 0.1) 0, rgba(255, 255, 255, 0.1) 68%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 100%);
    }

    .record.gold {
      background-color: goldenrod;
      background-image: radial-gradient(circle, #eee 0, #eee 1.8%, rgba(17, 17, 17, 0.75) 0, rgba(17, 17, 17, 0.75) 24%, rgba(255, 255, 255, 0) 24%, rgba(255, 255, 255, 0) 100%), conic-gradient(rgba(255, 255, 255, 0.45) 0, rgba(255, 255, 255, 0.45) 5%, rgba(255, 255, 255, 0.75) 8%, rgba(255, 255, 255, 0.75) 18%, rgba(255, 255, 255, 0.15) 24%, rgba(255, 255, 255, 0.15) 32%, rgba(255, 255, 255, 0.3) 34%, rgba(255, 255, 255, 0.3) 42%, rgba(255, 255, 255, 0.15) 46%, rgba(255, 255, 255, 0.15) 56%, rgba(255, 255, 255, 0.45) 64%, rgba(255, 255, 255, 0.45) 69%, rgba(255, 255, 255, 0.15) 72%, rgba(255, 255, 255, 0.15) 81%, rgba(255, 255, 255, 0.3) 84%, rgba(255, 255, 255, 0.3) 86%, rgba(255, 255, 255, 0.15) 90%, rgba(255, 255, 255, 0.15) 95%, rgba(255, 255, 255, 0.45) 98%, rgba(255, 255, 255, 0.45) 100%), radial-gradient(circle, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 29%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 34.5%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 42%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 42.5%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 48%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 48.25%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 54.5%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 55%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 59%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 59.5%, rgba(255, 255, 255, 0.15) 0, rgba(255, 255, 255, 0.15) 68%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 100%);
      background-blend-mode: overlay;
    }

    .cd {
      background-image: radial-gradient(circle, #eee 0, #eee 10%, rgba(17, 17, 17, 0.2) 0, rgba(17, 17, 17, 0.2) 11.5%, rgba(255, 255, 255, 0.25) 0, rgba(255, 255, 255, 0.25) 17%, rgba(17, 17, 17, 0.05) 0, rgba(17, 17, 17, 0.05) 17.5%, rgba(255, 255, 255, 0.25) 0, rgba(255, 255, 255, 0.25) 20%, rgba(17, 17, 17, 0.05) 0, rgba(17, 17, 17, 0.05) 20.5%, rgba(255, 255, 255, 0.25) 0, rgba(255, 255, 255, 0.25) 25%, rgba(17, 17, 17, 0.2) 0, rgba(17, 17, 17, 0.2) 30%, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0) 69.5%, rgba(17, 17, 17, 0.15) 0, rgba(17, 17, 17, 0.2) 100%), conic-gradient(#ffffff, #f5e1f6, #ffefd1, #fefec5, #c8c8c8, #8a8a8a, #605859, #704864, #3b49ac, #31c8de, #6cd399, #d0d0d0, #fcfcfc, #ffffff, #ffffff, #eafae9, #a3ffef, #98bcea, #9997a4, #868686, #767676, #f0ed5b, #e2a3a7, #d4d4d4, #ffffff, #ffffff);
    }
  </style>
</head>

<body>
  <h2 class="desc" style="padding-top: 50px">请选择一首本地音频文件</h2>
  <div id="content">
    <input type="file" id="file" accept="audio/*" />
    <canvas id="canvas"></canvas>
    <audio id="audio" controls></audio>
  </div>
  <div class="cd disc"></div>
  <script type="text/javascript">

    window.onload = function () {

      var file = document.getElementById("file");
      var audio = document.getElementById("audio");

      file.onchange = function () {
        //part1: 画布
        var canvas = document.getElementById("canvas");
        var context = canvas.getContext("2d");

        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        var WIDTH = canvas.width;
        var HEIGHT = canvas.height;

        //part2: 音频
        var files = this.files;//声音文件
        audio.src = URL.createObjectURL(files[0]);
        audio.load();

        //part3: 分析器
        var AudCtx = new AudioContext();//音频内容
        var src = AudCtx.createMediaElementSource(audio);
        var analyser = AudCtx.createAnalyser();

        src.connect(analyser);
        analyser.connect(AudCtx.destination);
        analyser.fftSize = 128;//快速傅里叶变换, 必须为2的N次方

        var bufferLength = analyser.frequencyBinCount;// = fftSize * 0.5

        //part4: 变量
        var barWidth = (WIDTH / bufferLength) - 1;//间隔1px
        var barHeight;

        var dataArray = new Uint8Array(bufferLength);//8位无符号定长数组

        //part5: 动态监听
        function renderFrame() {
          requestAnimationFrame(renderFrame);//方法renderFrame托管到定时器，无限循环调度，频率<16.6ms/次

          context.fillStyle = "#000";//黑色背景
          context.fillRect(0, 0, WIDTH, HEIGHT);//画布拓展全屏,动态调整

          analyser.getByteFrequencyData(dataArray);//获取当前时刻的音频数据

          //part6: 绘画声压条
          var x = 0;
          for (var i = 0; i < bufferLength; i++) {
            var data = dataArray[i];//int,0~255

            var percentV = data / 255;//纵向比例
            var percentH = i / bufferLength;//横向比例

            barHeight = HEIGHT * percentV;

            //gbk,0~255
            var r = 255 * percentV;//值越大越红
            var g = 255 * percentH;//越靠右越绿
            var b = 50;

            context.fillStyle = "rgb(" + r + "," + g + "," + b + ")";
            context.fillRect(x, HEIGHT - barHeight, barWidth, barHeight);

            x += barWidth + 1;//间隔1px
          }
        }

        renderFrame();

        //part7: 播放声音
        audio.play();
      };
    };
  </script>
</body>

</html>